﻿#region << 文件说明 >>

/*----------------------------------------------------------------
 * 命名空间：Vampirewal.BlogCore
 * 唯一标识：7090213d-da69-4abf-b49d-601bc7b73f8f
 * 文件名：HtmlConfig
 *
 * 创建者：杨程
 * 电子邮箱：235160615@qq.com
 * 创建时间：2024/6/13 17:44:14
 * 版本：V1.0.0
 * 描述：
 *
 * ----------------------------------------------------------------
 * 修改人：
 * 时间：
 * 修改说明：
 *
 * 版本：V1.0.1
 *----------------------------------------------------------------*/

#endregion << 文件说明 >>

using System.Security.Cryptography;

namespace Vampirewal.BlogCore;

/// <summary>
/// html配置
/// </summary>
public sealed class HtmlConfig
{
    internal HtmlConfig()
    {
        Title = "Vampirewal.Blog";
        Keywords = "Vampirewal.Blog";
        Description = "Vampirewal.Blog";
        IcoPath = $"/assets/logo.ico";
        LogoPath = $"/assets/Vampirewal-Logo.png";

        PageDic = new Dictionary<string, PageBase>();
        PageCacheDic = new Dictionary<string, string>();
        leftTreeMenus = new List<LeftTreeMenu>();
        TopMenus = new List<TopMenu>();

        var option = App.Service.GetRequiredService<IBlogOption<BlogOptions>>();

        _blogOption = option.GetValue();

        RegisterAllPage();
    }

    /// <summary>
    /// 博客配置项
    /// </summary>
    private BlogOptions _blogOption { get; set; }

    /// <summary>
    /// 网络地址
    /// </summary>
    internal string LocalHost { get; set; }

    #region 标题

    /// <summary>
    /// 标题
    /// </summary>
    internal string Title { get; set; }

    /// <summary>
    /// 设置标题
    /// </summary>
    /// <param name="title"></param>
    /// <returns></returns>
    public HtmlConfig SetTitle(string title)
    {
        Title = title;
        return this;
    }

    #endregion

    #region 是否开启鉴权

    /// <summary>
    /// 是否页面进行鉴权
    /// </summary>
    internal bool LoginAuthCheck { get; set; }

    /// <summary>
    /// 设置是否开启登陆鉴权功能
    /// </summary>
    /// <param name="secretKey">密钥</param>
    /// <param name="signatureKey">签名key</param>
    /// <param name="tokenExpiry">过期时间</param>
    /// <returns></returns>
    public HtmlConfig SetOpenLoginAuth(string secretKey,string signatureKey, TimeSpan tokenExpiry)
    {
        LoginAuthCheck= true;
        SecretKey = secretKey;
        TokenExpiry = tokenExpiry;
        SignatureKey= signatureKey;
        return this;
    }

    /// <summary>
    /// 鉴权密钥
    /// </summary>
    internal string SecretKey { get; set; }

    /// <summary>
    /// 过期时间
    /// </summary>
    internal TimeSpan TokenExpiry { get; set; }

    /// <summary>
    /// 签名key
    /// </summary>
    internal string SignatureKey { get; set; }

    internal string GenerateToken()
    {
        var token = $"{SignatureKey}_{DateTime.UtcNow.Add(TokenExpiry):o}_{GenerateSignature(SignatureKey)}";
        return Convert.ToBase64String(Encoding.UTF8.GetBytes(token));
    }

    private string GenerateSignature(string key)
    {
        using (var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(SecretKey)))
        {
            var data = Encoding.UTF8.GetBytes(key);
            var hash = hmac.ComputeHash(data);
            return Convert.ToBase64String(hash);
        }
    }

    /// <summary>
    /// 验证token
    /// </summary>
    /// <param name="token"></param>
    /// <returns></returns>
    internal bool ValidateToken(string token)
    {
        try
        {
            // 解码 Token
            var tokenBytes = Convert.FromBase64String(token);
            var tokenString = Encoding.UTF8.GetString(tokenBytes);

            var parts = tokenString.Split('_');
            if (parts.Length != 3)
            {
                return false; // 格式不正确
            }

            var signatureKey = parts[0];
            var expiry = DateTime.Parse(parts[1]);
            var signature = parts[2];

            // 验证是否过期
            if (DateTime.UtcNow > expiry)
            {
                return false; // Token 已过期
            }

            // 验证签名
            var expectedSignature = GenerateSignature(signatureKey);
            return expectedSignature == signature;
        }
        catch
        {

            return false;
        }
        
    }

    #endregion

    #region 关键词

    /// <summary>
    /// 关键词
    /// </summary>
    internal string Keywords { get; set; }

    /// <summary>
    /// 设置关键词
    /// </summary>
    /// <param name="keywords"></param>
    /// <returns></returns>
    public HtmlConfig SetKeywords(string keywords)
    {
        Keywords = keywords;
        return this;
    }

    #endregion

    #region 描述

    /// <summary>
    /// 描述信息
    /// </summary>
    internal string Description { get; set; }

    /// <summary>
    /// 设置描述信息
    /// </summary>
    /// <param name="description"></param>
    /// <returns></returns>
    public HtmlConfig SetDescription(string description)
    {
        Description = description;
        return this;
    }

    #endregion

    #region Ico图标

    /// <summary>
    /// Ico图标
    /// </summary>
    internal string IcoPath { get; set; }

    /// <summary>
    /// 设置Ico图标
    /// </summary>
    /// <param name="icoPath"></param>
    /// <returns></returns>
    public HtmlConfig SetIcoPath(string icoPath)
    {
        IcoPath = icoPath;
        return this;
    }

    #endregion

    #region logo路径

    /// <summary>
    /// logo路径
    /// </summary>
    internal string LogoPath { get; set; }

    /// <summary>
    /// 设置logo
    /// </summary>
    /// <param name="logoPath"></param>
    /// <returns></returns>
    public HtmlConfig SetLogoPath(string logoPath)
    {
        LogoPath = logoPath;
        return this;
    }

    #endregion

    #region 顶部菜单

    /// <summary>
    /// 顶部菜单
    /// </summary>
    internal List<TopMenu> TopMenus { get; set; }

    /// <summary>
    /// 设置顶部右侧菜单
    /// </summary>
    /// <param name="topmenu"></param>
    /// <returns></returns>
    public HtmlConfig SetTopMenu(Action<List<TopMenu>> topmenu)
    {
        topmenu?.Invoke(TopMenus);

        return this;
    }

    #endregion

    #region 页面

    /// <summary>
    /// 左侧菜单数据源
    /// </summary>
    internal List<LeftTreeMenu> leftTreeMenus { get; set; }

    /// <summary>
    /// 页面字典
    /// </summary>
    internal Dictionary<string, PageBase> PageDic { get; set; }

    /// <summary>
    /// 页面缓存字典
    /// </summary>
    internal Dictionary<string, string> PageCacheDic { get; set; }

    /// <summary>
    /// 注册全部页面
    /// </summary>
    /// <exception cref="Exception"></exception>
    private void RegisterAllPage()
    {
        var types = AppDomain.CurrentDomain.GetAssemblies().SelectMany(a => a.GetTypes()).Where(w => w.BaseType == typeof(PageBase)).ToArray();

        List<PageBase> CurPageBase = new List<PageBase>();

        foreach (var type in types)
        {
            var page = (ActivatorUtilities.CreateInstance(App.Service, type) as PageBase)!;

            if (string.IsNullOrEmpty(page.Title))
                throw new Exception("Title不能为空");

            if (string.IsNullOrEmpty(page.MenuGroup))
                throw new Exception("MenuGroup不能为空");

            CurPageBase.Add(page);
        }

        CurPageBase.Sort((s1, s2) =>
        {
            if (s1.Sort > s2.Sort)
                return 1;

            return -1;
        });


        foreach (var page in CurPageBase)
        {
            var group = leftTreeMenus.FirstOrDefault(f => f.Name == page.MenuGroup);

            if (group == null)
            {
                group = new LeftTreeMenu()
                {
                    Id = $"{page.Sort}",
                    Name = page.MenuGroup,
                    Children = new List<LeftTreeMenu>(),
                    Sort = page.Sort
                };

                if (leftTreeMenus.Any(a => a.Sort == page.Sort))
                    throw new Exception($"{page.Title}的Sort值重复！");

                leftTreeMenus.Add(group);
            }

            if (group != null)
            {
                LeftTreeMenu curMenu = new LeftTreeMenu()
                {
                    Id = $"{group.Id}-{group.Children.Count + 1}",
                    Name = page.Title,
                    Sort = page.Sort
                };

                group.Children.Add(curMenu);

                PageDic.Add(curMenu.Id, page);

                if (page.IsCache)
                {
                    PageCacheDic.Add(curMenu.Id, page.ToHtml());
                }
            }
        }

    }

    internal string LeftMenuComponent()
    {
        StringBuilder sb = new StringBuilder();

        sb.AppendLine("<el-menu default-active=\"0-1\" class=\"el-menu-vertical-demo\"  @select=\"handleSelect\" unique-opened=\"true\">");

        foreach (var item in leftTreeMenus)
        {
            sb.AppendLine($"<el-sub-menu index=\"{item.Id}\">");

            sb.AppendLine($"    <template #title>");
            sb.AppendLine($"        <el-icon><location /></el-icon>");
            sb.AppendLine($"        <span>{item.Name}</span>");
            sb.AppendLine($"    </template>");
            sb.AppendLine($"    <el-menu-item-group title=\"\">");

            foreach (var child in item.Children)
            {
                sb.AppendLine($"    <el-menu-item index=\"{child.Id}\">{child.Name}</el-menu-item>");
            }

            sb.AppendLine($"    </el-menu-item-group>");

            sb.AppendLine("</el-sub-menu>");
        }

        sb.AppendLine("</el-menu>");

        return sb.ToString();
    }

    internal string TopMenuComponent()
    {
        StringBuilder sb = new StringBuilder();

        //sb.AppendLine($"<div>");
        sb.AppendLine($"    <el-menu class=\"el-menu\" mode=\"horizontal\" :ellipsis=\"false\" :default-active=\"0\">");
        sb.AppendLine($"        <el-menu-item index=\"0\" class=\"el-menu-item\">");
        sb.AppendLine($"            <img style=\"width: 50px\" src=\"{LogoPath}\" alt=\"{Title}\" />");
        sb.AppendLine($"            <span style=\"margin-left:10px;\"><el-link href=\"{_blogOption.ApiUrl}\"  underline=\"false\">{Title}</el-link></span>");
        sb.AppendLine($"        </el-menu-item>");
        sb.AppendLine($"        <div class=\"flex-grow\" style=\"flex-grow: 1;\"></div>");

        Action<TopMenu>? action = null;

        action = new Action<TopMenu>(t =>
        {
            if (t.Control == null)
            {
                if (t.Children != null && t.Children.Count > 0)
                {
                    sb.AppendLine($"<el-sub-menu index=\"{t.Id}\" class=\"el-sub-menu\">");
                    sb.AppendLine($"<template #title>{t.Name}</template>");

                    foreach (var item in t.Children)
                    {
                        if (item.Children != null && item.Children.Count > 0)
                        {
                            action?.Invoke(item);
                        }
                        else
                        {
                            if (item.Control == null)
                            {
                                sb.AppendLine($"<el-menu-item index=\"{item.Id}\" class=\"el-menu-item\">{item.Name}</el-menu-item>");
                            }
                            else
                            {
                                sb.AppendLine($"<el-menu-item index=\"{item.Id}\" class=\"el-menu-item\">{item.Control.ConverToHtml()}</el-menu-item>");
                            }
                        }
                    }

                    sb.AppendLine($"</el-sub-menu>");
                }
                else
                {
                    sb.AppendLine($"        <el-menu-item index=\"{t.Id}\" class=\"el-menu-item\">{t.Name}</el-menu-item>");
                }
            }
            else
            {
                if (t.Children != null && t.Children.Count > 0)
                {
                    sb.AppendLine($"<el-sub-menu index=\"{t.Id}\" class=\"el-sub-menu\">");
                    sb.AppendLine($"<template #title>{t.Name}</template>");

                    foreach (var item in t.Children)
                    {
                        if (item.Children != null && item.Children.Count > 0)
                        {
                            action?.Invoke(item);
                        }
                        else
                        {
                            sb.AppendLine($"<el-menu-item index=\"{item.Id}\" class=\"el-menu-item\">{item.Control.ConverToHtml()}</el-menu-item>");
                        }
                    }

                    sb.AppendLine($"</el-sub-menu>");
                }
                else
                {
                    sb.AppendLine($"        <el-menu-item index=\"{t.Id}\" class=\"el-menu-item\">{t.Control.ConverToHtml()}</el-menu-item>");
                }
            }
        });

        foreach (var item in TopMenus)
        {
            action?.Invoke(item);
        }

        //sb.AppendLine($"        <el-menu-item index=\"1\" class=\"el-menu-item\" ><el-link href=\"https://www.baidu.com\" target=\"_blank\" underline=\"false\">Processing Center1</el-link></el-menu-item>");
        //sb.AppendLine($"        <el-menu-item index=\"2\" class=\"el-menu-item\">Processing Center2</el-menu-item>");
        sb.AppendLine($"    </el-menu>");
        //sb.AppendLine($"</div>");

        return sb.ToString();
    }

    private string LoadScripts(bool useCDN)
    {
        StringBuilder sb = new StringBuilder();

        if(useCDN)
        {
            sb.AppendLine($"<!-- CDN加载 -->");
            sb.AppendLine($"<!-- 导入vue3 -->");
            sb.AppendLine($"<script src=\"https://unpkg.com/vue@3\"></script>");
            sb.AppendLine($"<!-- 引入element-plus样式文件 -->");
            sb.AppendLine($"<link rel=\"stylesheet\" href=\"https://unpkg.com/element-plus/dist/index.css\" />");
            sb.AppendLine($"<!-- 引入element-plus组件库 -->");
            sb.AppendLine($"<script src=\"https://unpkg.com/element-plus\"></script>");
            sb.AppendLine($"<script src=\"https://unpkg.com/@element-plus/icons-vue\"></script>");
            sb.AppendLine($"<!-- 引入axios -->");
            sb.AppendLine($"<script src=\"https://unpkg.com/axios/dist/axios.min.js\"></script>");
            sb.AppendLine($"<link rel=\"stylesheet\" href=\"https://unpkg.com/@highlightjs/cdn-assets@11.9.0/styles/default.min.css\"/>");
            sb.AppendLine($"<script src=\"https://unpkg.com/@highlightjs/cdn-assets@11.9.0/highlight.min.js\"></script>");
            sb.AppendLine($"<script src=\"https://unpkg.com/@highlightjs/cdn-assets@11.9.0/languages/go.min.js\"></script>");
            sb.AppendLine($"<!-- 引入highlightjs -->");
            sb.AppendLine($"<script src=\"https://cdnjs.cloudflare.com/ajax/libs/highlightjs-line-numbers.js/2.8.0/highlightjs-line-numbers.min.js\"></script>");
        }
        else
        {
            sb.AppendLine($"<!-- 使用本地加载 -->");
            sb.AppendLine($"<!-- 导入vue3 -->");
            sb.AppendLine($"<script src=\"/Resource/vue.js\"></script>");
            sb.AppendLine($"<!-- 引入element-plus样式文件 -->");
            sb.AppendLine($"<link rel=\"stylesheet\" href=\"/Resource/index.css\" />");
            sb.AppendLine($"<!-- 引入element-plus组件库 -->");
            sb.AppendLine($"<script src=\"/Resource/index.full.js\"></script>");
            sb.AppendLine($"<script src=\"/Resource/index.iife.min.js\"></script>");
            sb.AppendLine($"<!-- 引入axios -->");
            sb.AppendLine($"<script src=\"/Resource/axios.min.js\"></script>");
            sb.AppendLine($"<link rel=\"stylesheet\" href=\"/Resource/default.min.css\"/>");
            sb.AppendLine($"<script src=\"/Resource/highlight.min.js\"></script>");
            sb.AppendLine($"<script src=\"/Resource/go.min.js\"></script>");
            sb.AppendLine($"<!-- 引入highlightjs -->");
            sb.AppendLine($"<script src=\"/Resource/highlightjs-line-numbers.min.js\"></script>");
        }

        return sb.ToString();
    }

    #endregion

    

    /// <summary>
    /// 
    /// </summary>
    /// <returns></returns>
    public override string ToString()
    {
        return $$$"""

            <!DOCTYPE html>
            <html lang="en">
                <head>
                    <meta charset="UTF-8">
                    <meta name="viewport" content="width=device-width, initial-scale=1.0">
                    <meta name="keywords" content="{{{Keywords}}}" />
                    <meta name="description" content="{{{Description}}}" />
                    <link rel="icon" type="image/x-icon" href="{{{IcoPath}}}" />
                    <link rel="stylesheet" href="/Resource/styles.css" />
                    <title>{{{Title}}}</title>

                    {{{LoadScripts(_blogOption.UseCDNLoad)}}}
                    
                </head>
                <body>
                    <div id="app" style="display:flex;flex-direction: column;min-height: 100vh;">
                        <header class="navbar has-sidebar" style="top: 0;left: 0;position: sticky;z-index: 12;">

                            {{{TopMenuComponent()}}}

                        </header>

                        <div style="width: calc(190px);z-index: 11;top:55px ;transform: translate(0);position: fixed;bottom: 10px;background-color: #ffffff;padding: 48px 32px;overflow-y:  auto;transition: background-color .2s,opacity .25s,transform .5s cubic-bezier(.19,1,.22,1);">

                            {{{LeftMenuComponent()}}}

                        </div>

                        <main id="page-content" style="outline: none;padding-top: 10px;padding-left: calc(16rem + 20px);display: block; padding-bottom: 30px;" >
                            <component :is="dynamicComponent"></component>
                        </main>

                    </div>

                    <script type="module">
                        const { createApp, ref,reactive  } = Vue;
                        //const { createRouter, createWebHashHistory } = VueRouter;

                        // 全局初始化 highlight
                        window.hljs = hljs;

                        // 开启代码高亮
                        //hljs.highlightAll();

                        // 行号显示
                        //hljs.initLineNumbersOnLoad();

                        const app= createApp({
                            setup() {
                                const dynamicComponent = ref()

                                return {
                                   dynamicComponent
                                }
                            },

                            created () {
                            },
                            mounted() {
                                this.handleSelect('0-1','0,0-1')
                            },
                            methods: {
                                handleSelect(key,keyPath){
                                    console.log('keyPath:'+keyPath)

                                    const request = axios.create({
                                        baseURL: '{{{_blogOption.ApiUrl}}}'
                                    })

                                    request.get('/api/getDetail/'+key)
                                           .then((response)=>{
                                                //console.log('数据获取成功', response.data)

                                                this.dynamicComponent={
                                                    template:response.data,
                                                    mounted() {
                                                        hljs.highlightAll();
                                                        hljs.initLineNumbersOnLoad();
                                                    },
                                                    data(){
                                                        const containerRef = ref(null);
                                                        //提前定义一个表单内容
                                                        const form = reactive({});
                                                        
                                                        return{
                                                            containerRef,request,form
                                                        }
                                                    },
                                                    methods:{
                                                        handleClick(e){
                                                            e.preventDefault()
                                                        },
                                                        
                                                    }
                                                }
                                           })
                                           .catch((error)=>{
                                                console.log(error)
                                                this.$message({ message: error.response.data,type: 'error'})
                                           })
                                },
                                

                            },
                        });

                        for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
                          app.component(key, component)
                        }

                        app.use(ElementPlus).mount('#app')

                    </script>
                </body>
            </html>

            """;
    }

    /// <summary>
    /// 获取登陆页
    /// </summary>
    /// <returns></returns>
    public string GetLoginHtml()
    {
        return $$$"""
            <!DOCTYPE html>
            <html lang="en">
                <head>
                    <meta charset="UTF-8">
                    <meta name="viewport" content="width=device-width, initial-scale=1.0">
                    <meta name="keywords" content="{{{Keywords}}}" />
                    <meta name="description" content="{{{Description}}}" />
                    <link rel="icon" type="image/x-icon" href="{{{IcoPath}}}" />
                    <title>{{{Title}}}</title>
                    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
                <style>
                    body {
                        font-family: Avenir, Helvetica, Arial, sans-serif;
                        background-color: #f5f5f5;
                        display: flex;
                        justify-content: center;
                        align-items: center;
                        height: 100vh;
                        margin: 0;
                    }
                    .login-form {
                        width: 400px;
                        padding: 20px;
                        background-color: white;
                        border-radius: 10px;
                        box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
                    }
                    .login-form h2 {
                        text-align: center;
                        margin-bottom: 20px;
                    }
                    .login-form label {
                        display: block;
                        margin-bottom: 8px;
                    }
                    .login-form input {
                        width: 90%;
                        padding: 10px;
                        margin-bottom: 20px;
                        border: 1px solid #ccc;
                        border-radius: 5px;
                    }
                    .login-form button {
                        width: 100%;
                        padding: 10px;
                        background-color: #007bff;
                        color: white;
                        border: none;
                        border-radius: 5px;
                        cursor: pointer;
                        font-size: 16px;
                    }
                    .login-form button:hover {
                        background-color: #0056b3;
                    }
                </style>
            </head>
            <body>
                <div class="login-form">
                    <h2>用户登录</h2>
                    <form id="loginForm" onsubmit="handleLogin(event)">
                        <label for="username">用户名</label>
                        <input type="text" id="username" placeholder="请输入用户名" required>
                        <label for="password">密码</label>
                        <input type="password" id="password" placeholder="请输入密码" required>
                        <button type="submit">登录</button>
                    </form>
                </div>
            
                <script>
                    function handleLogin(event) {
                        event.preventDefault(); // 阻止默认提交行为
                        const username = document.getElementById('username').value;
                        const password = document.getElementById('password').value;
            
                        const request = axios.create({
                                        baseURL: '{{{_blogOption.ApiUrl}}}'
                        })
            
                        request.get('/login?username='+username+'&password='+password)
                               .then((response)=>{
                                                const data = response.data;
                                                console.log('数据获取成功', response.data)
                                                localStorage.setItem('token', data.token);
                                                // 将 token 添加到 cookie 中
                                                document.cookie = `token=${data.token}; path=/;`;
                                                window.location.reload(); // 登录成功后刷新页面
                                           })
                                           .catch((error)=>{
                                                console.log(error);
                                                alert('登录失败，请检查用户名或密码。');
                                           })
            
            
                        
                    }
                </script>
            </body>
            </html>

            """;
    }
}


public sealed class LeftTreeMenu
{
    public string Id { get; set; }

    public string Name { get; set; }

    public int Sort { get; set; }

    public List<LeftTreeMenu> Children { get; set; }
}

public sealed class TopMenu
{
    public string Id { get; set; }

    public string Name { get; set; }

    public List<TopMenu> Children { get; set; }

    /// <summary>
    /// Name和control只会存在1个，如果存在Control那就使用Control
    /// </summary>
    public ControlBase Control { get; set; }
}